﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;

namespace SharpObjects.Data.EntityMapping
{
    public class EntityFactory : IEntityFactory
    {
        static EntityFactory()
        {
            EntityInfoMap = new Dictionary<Type, EntityInfo>();
        }

        static Dictionary<Type, EntityInfo> EntityInfoMap { get; set; }

        static EntityInfo GetEntityInfo(Type t)
        {
            if(EntityInfoMap.ContainsKey(t))
            {
                return EntityInfoMap[t];
            }
            else
            {
                EntityInfo entityInfo = new EntityInfo();
                EntityInfoMap[t] = entityInfo;

                entityInfo.Properties = t.FindMembers(MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance, new MemberFilter(DelegateToSearchCriteria), null);

                for(int k = 0; k < entityInfo.Properties.Length; k++)
                {
                    object[] attribs = entityInfo.Properties[k].GetCustomAttributes(typeof (MappingAttribute), false);
                    entityInfo.Attributes[entityInfo.Properties[k]] = attribs;
                }

                return entityInfo;
            }
        }

        public List<T> GetObjects<T>(DataRowCollection rows)
        {
            List<T> objs = new List<T>();
            EntityInfo entityInfo = GetEntityInfo(typeof (T));
            
            for(int k = 0; k < rows.Count; k++)
            {
                T obj = GetObject<T>(rows[k], entityInfo);
                objs.Add(obj);
            }

            return objs;
        }

        public T GetObject<T>(DataRow row)
        {
            EntityInfo entityInfo = GetEntityInfo(typeof (T));
            return GetObject<T>(row, entityInfo);
        }

        public T GetObject<T>(DataRow row, EntityInfo entityInfo)
        {
            Type t = typeof (T);
            T obj = (T)t.Assembly.CreateInstance(t.FullName);

            for (int k = 0; k < entityInfo.Properties.Length; k++ )
            {
                InitProperty(entityInfo.Properties[k], ref obj, row, entityInfo);    
            }

            if(obj is IDbEntity)
            {
                ((IDbEntity) obj).DbRow = row;
            }

            return obj; 
        }

        private static void InitProperty<T>(MemberInfo memberInfo, ref T obj, DataRow row, EntityInfo entityInfo)
        {
            object[] attribs = entityInfo.Attributes[memberInfo];

            if (attribs != null && attribs.Length > 0)
            {
                MappingAttribute mappingAttribute = (MappingAttribute) attribs[0];
                string columnName = mappingAttribute.DbColumn;

                if (memberInfo is PropertyInfo)
                {
                    PropertyInfo propertyInfo = (PropertyInfo) memberInfo;

                    object[] args = new object[1];

                    if (row[columnName] == DBNull.Value)
                    {
                        args[0] = null;
                    } 
                    else if (propertyInfo.PropertyType == typeof(string))
                    {
                        args[0] = row[columnName].ToString();
                    }
                    else if (propertyInfo.PropertyType == typeof(char))
                    {
                        args[0] = Convert.ToChar(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(bool))
                    {
                        args[0] = Convert.ToBoolean(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(byte))
                    {
                        args[0] = Convert.ToByte(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof (Int16))
                    {
                        args[0] = Convert.ToInt16(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(Int32))
                    {
                        args[0] = Convert.ToInt32(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(Int64))
                    {
                        args[0] = Convert.ToInt64(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(float))
                    {
                        args[0] = (float)Convert.ToDouble(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(double))
                    {
                        args[0] = Convert.ToDouble(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(decimal))
                    {
                        args[0] = Convert.ToDecimal(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(DateTime))
                    {
                        args[0] = Convert.ToDateTime(row[columnName]);
                    }
                    else if (propertyInfo.PropertyType == typeof(DateTime?))
                    {
                        args[0] = Convert.ToDateTime(row[columnName]);
                    }
                    else
                    {
                        throw new ArgumentException("Unsupported data type:" + propertyInfo.PropertyType.Name);
                    }

                    Type t = typeof (T);
                    t.InvokeMember(memberInfo.Name, BindingFlags.SetProperty, null, obj, args);

                }
            }
        }

        public void SaveObject<T>(IDb db, T obj)
        {
            if(obj is IDbEntity)
            {
                IDbEntity dbEntity = (IDbEntity) obj;
                if(dbEntity.DbRow != null)
                {
                    UpdateObject(db, obj);
                }
                else
                {
                    InsertObject(db, obj);
                }
            }
            else
            {
                InsertObject(db, obj);                
            }
        }

        public void InsertObject<T>(IDb db, T obj)
        {
            
        }

        public void UpdateObject<T>(IDb db, T obj)
        {
            
        }

        private static bool DelegateToSearchCriteria(MemberInfo objMemberInfo, Object objSearch)
        {
            object [] attribs = objMemberInfo.GetCustomAttributes(typeof (MappingAttribute), false);

            if(attribs != null && attribs.Length > 0)
            {
                return true;
            }
            else
            {
                return false;                
            }
        }

    }
}
